package main.java.br.com.projeto.domain.dao;

import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.NoResultException;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaQuery;

public abstract class Dao<T> implements Serializable {
	
	private static final EntityManagerFactory factory = Persistence.createEntityManagerFactory("TesteJPA");
	protected static EntityManager manager = null;
	
	private Class<T> entityClass;
	
	public Dao(Class<T> entityClass) {
		this.entityClass = entityClass;
	}
	
	public static EntityManager getManager() {
		if ((manager == null) || (!manager.isOpen())) {
			manager = factory.createEntityManager();
		}
		return manager;
	}
	
	public void beginTransaction() {
		if (!getManager().getTransaction().isActive()) {
			getManager().getTransaction().begin();
		}
	}
	
	public void commit() {
		getManager().getTransaction().commit();
	}
	
	public void rollback() {
		getManager().getTransaction().rollback();
		this.closeTransaction();
	}
	
	public void closeTransaction() {
		getManager().close();
	}
	
	public void commitAndCloseTransaction() {
		this.commit();
		this.closeTransaction();
	}
	
	public void flush() {
		getManager().flush();
	}
	
	public void joinTransaction() {
		manager = factory.createEntityManager();
		manager.joinTransaction();
	}
	
	public T insert(T entity) {
		try {
			this.beginTransaction();
			getManager().persist(entity);
			this.commit();
			return entity;
		} catch (RuntimeException e) {
			getManager().getTransaction().rollback();
			throw e;
		}
		finally {
			getManager().close();
		}
	}
	
	public T update(T entity) {
		RuntimeException e = null;
		try {
			this.beginTransaction();		
			getManager().merge(entity);
			this.commit();
			return entity;
		}  catch (RuntimeException r) {
			e = r;
			getManager().getTransaction().rollback();
		}
		finally {
			getManager().close();
			if ( e!=null)
				throw e;
			
		}
		return null;
	}
	
	public void remove(T entity) {
		try {
			this.beginTransaction();			
			T entityToBeRemoved = getManager().merge(entity);
			getManager().remove(entityToBeRemoved);		
			this.commit();
		} catch (RuntimeException  e) {
			getManager().getTransaction().rollback();
			throw e;
		}
		finally {
			getManager().close();
		}
	}	
	
	public T find(Long entityID) {
		try {
			this.beginTransaction();
			T returnEntity = getManager().find(entityClass, entityID);
			return returnEntity;
		} catch (Exception e) {
			this.rollback();
			e.printStackTrace();
		} finally {
			getManager().close();
		}
		return null;	
	}
	
	public T findReferenceOnly(Long entityID) {
		try {
			this.beginTransaction();
			T returnEntity = getManager().getReference(entityClass, entityID);
			return returnEntity;
		} catch (Exception e) {
			this.rollback();
			e.printStackTrace();
		} finally {
			getManager().close();
		}
		return null;
	}
	
	// Using the unchecked because JPA does not have a
	// em.getCriteriaBuilder().createQuery()<T> method
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public List<T> findAll() {
		try {
			this.beginTransaction();		
			CriteriaQuery cq = getManager().getCriteriaBuilder().createQuery();
			cq.select(cq.from(entityClass));
			List<T> returnList = getManager().createQuery(cq).getResultList();
			return returnList;
		} catch (Exception e) {
			this.rollback();
			e.printStackTrace();
		}
		finally {
			getManager().close();
		}
		return null;
	}

	// Using the unchecked because JPA does not have a
	// query.getSingleResult()<T> method
	@SuppressWarnings("unchecked")
	protected List<T> findOneResult(String namedQuery, Map<String, Object> parameters) {
		this.beginTransaction();
		
		List<T> result = null;

		try {
			Query query = getManager().createQuery(namedQuery);

			// Method that will populate parameters if they are passed not null and empty
			if (parameters != null && !parameters.isEmpty()) {
				populateQueryParameters(query, parameters);
			}

			result = query.getResultList();
			return result;
			
		} catch (NoResultException e) {
			System.out.println("No result found for named query: " + namedQuery);
		} catch (Exception e) {
			System.out.println("Error while running query: " + e.getMessage());
			e.printStackTrace();
		} finally {
			getManager().close();
		}
		
		return null;
		
	}

	private void populateQueryParameters(Query query, Map<String, Object> parameters) {
		for (Entry<String, Object> entry : parameters.entrySet()) {
			query.setParameter(entry.getKey(), entry.getValue());
		}
	}
}
